﻿
Imports System.Net
Imports std

Public Class hddoslite
    Inherits ddoslite

    Private bypassPaths As vector(Of String) = Nothing

    Private Class parameter
        Public enable As Boolean
        Public queuelength As Int64
        Public rejectOverallPercent As Double
        Public rejectCurrentPercent As Double
        Public fakeInsertIntervalMS As Int64
    End Class

    Public Enum rejectIssue
        url = 0
        resource = 1
        ip = 2
        referer = 3
    End Enum

    Private Shared Function isRejectIssue(ByVal rejectIssuesByte As Byte, ByVal issue As rejectIssue) As Boolean
        Return bithelper.getbit(rejectIssuesByte, issue)
    End Function

    Private Shared Sub splitRejectIssues(ByVal rejectIssuesByte As Byte, ByRef issue As rejectIssue, _
                                         ByRef output As vector(Of rejectIssue))
        If isRejectIssue(rejectIssuesByte, issue) Then
            output.push_back(issue)
        End If
    End Sub

    Public Shared Sub splitRejectIssues(ByVal rejectIssuesByte As Byte, ByRef rejectIssues As vector(Of rejectIssue))
        If rejectIssues Is Nothing Then
            rejectIssues = New vector(Of rejectIssue)()
        Else
            rejectIssues.clear()
        End If

        splitRejectIssues(rejectIssuesByte, rejectIssue.url, rejectIssues)
        splitRejectIssues(rejectIssuesByte, rejectIssue.resource, rejectIssues)
        splitRejectIssues(rejectIssuesByte, rejectIssue.ip, rejectIssues)
        splitRejectIssues(rejectIssuesByte, rejectIssue.referer, rejectIssues)
    End Sub

    Public Shared Function rejectIssues(ByVal rejectIssuesByte As Byte) As String
        Const separator As String = ", "
        Dim msg As String = Nothing
        If isRejectIssue(rejectIssuesByte, rejectIssue.url) Then
            msg += separator + ddoskeyNaming.url
        End If
        If isRejectIssue(rejectIssuesByte, rejectIssue.resource) Then
            msg += separator + ddoskeyNaming.resource
        End If
        If isRejectIssue(rejectIssuesByte, rejectIssue.ip) Then
            msg += separator + ddoskeyNaming.ip
        End If
        If isRejectIssue(rejectIssuesByte, rejectIssue.referer) Then
            msg += separator + ddoskeyNaming.referer
        End If

        If Not isemptystring(msg) Then
            msg = strmid(msg, strlen(separator))
        End If
        Return msg
    End Function

    Private url As parameter = Nothing
    Private resource As parameter = Nothing
    Private ip As parameter = Nothing
    Private referer As parameter = Nothing
    Private Shadows _resourceCount As UInt64 = 0
    Private bypassInternalRequest As Boolean = False

    Private Sub readconfig(ByVal config As configParser, ByVal section As String, _
                           ByVal rulename As String, ByRef p As parameter)
        assert(Not config Is Nothing, "config is nothing.")
        If p Is Nothing Then
            p = New parameter()
        End If
        p.enable = config.parameter(section, rulename + configNaming.hddos.enable, _
                                    configNaming.defaultValue.hddos.enable)
        p.queuelength = config.parameter(section, rulename + configNaming.hddos.queuelength, _
                                         configNaming.defaultValue.hddos.queuelength)
        p.rejectOverallPercent = config.parameter(section, rulename + configNaming.hddos.rejectOverallPercent, _
                                                  configNaming.defaultValue.hddos.rejectOverallPercent)
        p.rejectCurrentPercent = config.parameter(section, rulename + configNaming.hddos.rejectCurrentPercent, _
                                                  configNaming.defaultValue.hddos.rejectCurrentPercent)
        p.fakeInsertIntervalMS = config.parameter(section, rulename + configNaming.hddos.fakeInsertIntervalMS, _
                                                  configNaming.defaultValue.hddos.fakeInsertIntervalMS)
    End Sub

    Private Sub initial(ByVal config As configParser, ByVal section As String)
        readconfig(config, section, configNaming.hddos.url, url)
        readconfig(config, section, configNaming.hddos.resource, resource)
        readconfig(config, section, configNaming.hddos.ip, ip)
        readconfig(config, section, configNaming.hddos.referer, referer)
        assert(Not config Is Nothing, "config is nothing.")
        _resourceCount = config.parameter(section, configNaming.hddos.resourceCount, _
                                          configNaming.defaultValue.hddos.resourceCount)
        bypassInternalRequest = config.parameter(section, configNaming.hddos.bypassInternalRequest, _
                                                 configNaming.defaultValue.hddos.bypassInternalRequest)
    End Sub

    Public Sub New(ByVal config As configParser, Optional ByVal section As String = configNaming.hddos.name)
        bypassPaths = New vector(Of String)()
        If isemptystring(section) Then
            initial(config, configNaming.hddos.name)
        Else
            initial(config, section)
        End If
    End Sub

    Public Sub addBypassPaths(ByVal ParamArray paths() As String)
        If Not paths Is Nothing AndAlso paths.Length > 0 Then
            For i As Int64 = 0 To paths.Length - 1
                bypassPaths.push_back(paths(i))
            Next
        End If
    End Sub

    Private Function findkey(ByVal key As String) As parameter
        If strsame(key, ddoskeyNaming.url, False) Then
            Return url
        ElseIf strsame(key, ddoskeyNaming.resource, False) Then
            Return resource
        ElseIf strsame(key, ddoskeyNaming.ip, False) Then
            Return ip
        ElseIf strsame(key, ddoskeyNaming.referer, False) Then
            Return referer
        Else
            assert(False, "should cover all possibilities.")
            Return Nothing
        End If
    End Function

    Protected Overrides Function queuelength(ByVal key As String) As Long
        Dim p As parameter = Nothing
        p = findkey(key)
        assert(Not p Is Nothing, "p is a nothing parameter when search for key " + key)
        Return p.queuelength
    End Function

    Protected Overrides Function rejectOverallPercent(ByVal key As String) As Double
        Dim p As parameter = Nothing
        p = findkey(key)
        assert(Not p Is Nothing, "p is a nothing parameter when search for key " + key)
        Return p.rejectOverallPercent
    End Function

    Protected Overrides Function rejectCurrentPercent(ByVal key As String) As Double
        Dim p As parameter = Nothing
        p = findkey(key)
        assert(Not p Is Nothing, "p is a nothing parameter when search for key " + key)
        Return p.rejectCurrentPercent
    End Function

    Protected Overrides Function fakeInsertIntervalMS(ByVal key As String) As Long
        Dim p As parameter = Nothing
        p = findkey(key)
        assert(Not p Is Nothing, "p is a nothing parameter when search for key " + key)
        Return p.fakeInsertIntervalMS
    End Function

    Protected Overrides Function resourceCount() As ULong
        Return _resourceCount
    End Function

    Private Function registerUrlEnable(ByVal u As String) As Boolean
        Return url.enable
    End Function

    Private Function registerResourceEnable(ByVal r As String) As Boolean
        Return resource.enable
    End Function

    Private Function registerIpEnable(ByVal i As String) As Boolean
        Return ip.enable
    End Function

    Private Function registerRefererEnable(ByVal r As String) As Boolean
        Return referer.enable AndAlso Not isemptystring(r)
    End Function

    Private Function bypassRequest(ByVal context As HttpListenerContext) As Boolean
        If bypassInternalRequest AndAlso isInternalRequest(context) Then
            Return True
        End If

        Dim path As String = Nothing
        path = context.Request.Url.AbsolutePath
        For i As Int64 = 0 To bypassPaths.size() - 1
            If strstartwith(path, bypassPaths(i)) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Shadows Function registerAccessing(ByVal context As HttpListenerContext, _
                                              Optional ByRef rejectIssues As Byte = 0) As returnValue
        rejectIssues = 0
        'at least, white list any requests from internal network
        If bypassRequest(context) Then
            Return returnValue.accept
        End If

        Dim accept As Boolean = True
        Dim this As Boolean = True
        Dim s As String = Nothing
        assert(Not url Is Nothing, "url is a nothing parameter.")
        s = requestFullUrl(context)
        If registerUrlEnable(s) Then
            this = registerAccessingImpl(ddoskeyNaming.url, s)
            If Not this Then
                bithelper.setbit(rejectIssues, rejectIssue.url)
            End If
            accept = accept And this
        End If

        assert(Not resource Is Nothing, "resource is a nothing parameter.")
        s = requestResource(context)
        If registerResourceEnable(s) Then
            this = registerAccessingImpl(ddoskeyNaming.resource, s)
            If Not this Then
                bithelper.setbit(rejectIssues, rejectIssue.resource)
            End If
            accept = accept And this
        End If

        assert(Not ip Is Nothing, "ip is a nothing parameter.")
        s = requestIp(context)
        If registerIpEnable(s) Then
            this = registerAccessingImpl(ddoskeyNaming.ip, s)
            If Not this Then
                bithelper.setbit(rejectIssues, rejectIssue.ip)
            End If
            accept = accept And this
        End If

        assert(Not referer Is Nothing, "referer is a nothing parameter.")
        s = httpUtils.referer(context)
        If registerRefererEnable(s) Then
            this = registerAccessingImpl(ddoskeyNaming.referer, s)
            If Not this Then
                bithelper.setbit(rejectIssues, rejectIssue.referer)
            End If
            accept = accept And this
        End If

        If accept Then
            Return returnValue.accept
        Else
            Return returnValue.reject
        End If
    End Function

    Public Shadows Function releaseAccessing(ByVal context As HttpListenerContext) As returnValue
        If bypassRequest(context) Then
            Return returnValue.success
        End If

        Dim s As String = Nothing
        assert(Not url Is Nothing, "url is a nothing parameter.")
        s = requestFullUrl(context)
        If registerUrlEnable(s) Then
            releaseAccessingImpl(ddoskeyNaming.url, s)
        End If

        assert(Not resource Is Nothing, "resource is a nothing parameter.")
        s = requestResource(context)
        If registerResourceEnable(s) Then
            releaseAccessingImpl(ddoskeyNaming.resource, s)
        End If

        assert(Not ip Is Nothing, "ip is a nothing parameter.")
        s = requestIp(context)
        If registerIpEnable(s) Then
            releaseAccessingImpl(ddoskeyNaming.ip, s)
        End If

        assert(Not referer Is Nothing, "referer is a nothing parameter.")
        s = httpUtils.referer(context)
        If registerRefererEnable(s) Then
            releaseAccessingImpl(ddoskeyNaming.referer, s)
        End If

        Return returnValue.success
    End Function
End Class
